use super::user::UserInfo;
use mongodb::{
    bson::{doc, Document},
    Collection,
};
use tide::{http::Method, Error, Middleware, Next, Request, StatusCode};

pub struct RoleMiddleware {
    pub roles: Collection,
}

fn can_access(role: &Document, permission: &str, path: &str) -> tide::Result<bool> {
    let allowed = role
        .get_array(permission)?
        .into_iter()
        .filter_map(|val| val.as_str())
        .any(|route| path.starts_with(route));
    Ok(allowed)
}

#[tide::utils::async_trait]
impl<State: Clone + Send + Sync + 'static> Middleware<State> for RoleMiddleware {
    async fn handle(&self, req: Request<State>, next: Next<'_, State>) -> tide::Result {
        if let Some(user) = req.ext::<UserInfo>() {
            if let Some(role) = self.roles.find_one(doc! {"_id": &user.role}, None).await? {
                if role.get_bool("isAdmin") == Ok(true) {
                    Ok(next.run(req).await)
                } else {
                    let path = req.url().path();
                    if req.method() == Method::Get && can_access(&role, "read", path)?
                        || can_access(&role, "write", path)?
                    {
                        Ok(next.run(req).await)
                    } else {
                        Err(Error::from_str(
                            StatusCode::Unauthorized,
                            "Permission denied to the requested path",
                        ))
                    }
                }
            } else {
                Err(Error::from_str(StatusCode::Unauthorized, "Role not found"))
            }
        } else {
            // public paths
            Ok(next.run(req).await)
        }
    }
}
